XCMD in Think C
Volume Number: 5
Issue Number: 5
Column Tag: HyperChat™
XCMD Corner: XCMDs in Think C
By Donald Koscheka, Arthur Young & Company, MacTutor Contributing
Editor
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
Exploring XCMDs using just one development system is a lot like learning to play
music on just one instrument. This is unfortunate because the Macintosh offers a
virtual orchestra of development systems. As in learning to play a second instrument,
learning a new development system often boils down to nothing more than learning
how to transcribe the music.
Your programming environment is your instrument (writing a program is more
like writing a piece of music than playing it). If you happen to own a copy of MPW you
have no trouble using the code in this column because it’s all written for MPW. If you
use a different development system , you’ll need to transcribe my listings to play on
your particular development system.
In the past, I’ve been terribly guilty of catering to the MPW clique. My reasons
were perhaps not so subtle: I use MPW at work; I’m comfortable with it, and I know
that it directly supports an interface to XCMDs. After purchasing a copy of MPW 3.0,
I realized that I might be making a mistake. For starters, MPW requires an expensive
and time consuming learning curve; perhaps not everyone wants to spend several
hundred dollars for sophistication they may never need.
Other development systems offer features that are not available in MPW such as a
high quality source level debugger. Unfortunately several development systems lack
key capabilities like support for XCMDs. Although you might expect LightspeedC to
support XCMDs out of the box, it doesn’t. I’m not sure I understand the reason, but it
doesn’t matter. Adding XCMD support to any compiler should be a very simple job.
Afterall, an XCMD is just another type of resource. If LightspeedC can create
specialized resources such as window definitions and drivers, it already contains some
of the support we need.
• Setting up the project
As luck, or design foresight, would have it, LightspeedC supports a generalized
resource type called the “CODE” Resource. CODE resource projects are created like
any other project in LightspeedC with the exception that you specify the project as a
code resource in the set project type dialogue (see figure 1). You specify most of the
information that the resource manger wants in this dialogue.
The custom header option requires a little elaboration. The standard interface to
code resources branches to the entry point: main() . Selecting the Custom header
option causes the entry point to be the first function in the file in which your main()
is defined. XCMDs follow the second course as a matter of style.
Set the resource type to ‘XCMD’ or ‘XFCN’ according to the type of Hypercard
interface you want.
Figure 1. Setting up a Code Resource using the Set Project Type Dialogue
• Writing the XCMD
In LightspeedC, a code resource has the same form as a “C” program. You define
the entry point as:
pascal void main( paramPtr )
ParamPtr is a pointer to a HyperTalk XCMDBlock. From there, your code looks
like a vanilla “C” application. The same restrictions apply as on all CODE resources:
no globals or statically initialized strings. CODE resources, of any kind, have no
knowledge of globals at build time because they can’t make any assumptions about
which application will load them! Strings suffer the same fate - “C” allocates
statically defined strings in the application’s global pool. You’re not building an
application so you don’t have a global pool - you must either define strings the hard
way or load them in from the resource fork.
Listing 1 is a simple XCMD that returns the string “Hello World” to Hypercard.
While the code itself is wholly unimaginative, it does demonstrate that the interface to
Hypercard and the callback mechanism work satisfactorily. Anyhow there’s a time for
imagination and a time to just get things done. Porting an interface falls to the latter
category.
Include the header file, “HyperXCMD.h” (listing 2 ) in your code. This is the
standard interface to Hypercard transposed for LightspeedC. There aren’t a lot of
differences between the two as should be the case. Nonetheless this exercise proves
quite useful in scoping out the idioms of a particular language implementation.
• The Hypercard “Glue”
You need to create a project which at the least includes MacTraps, your XCMD
code and a special file called “XCMDGlue.c” (Listing 3) which interfaces (or glues)
your XCMD to Hypertalk’s callback mechanism. I took the liberty of translating
XCMDGlue.in.c from MPW “C” to LightspeedC. The major difference between the MPW
and Think versions of the glue is that I use the CallPascal function available in
LightspeedC to jump to the subroutine pointed to by paramPtr->entryPoint. Pascal
routines push parameters from left to right and the subroutine is responsible for
clearing the stack parameters. “C” pushes from right to left and the caller clears the
stack.
We know that the callback engine uses the Pascal calling sequence because the
parameters aren’t left on the stack after the call. Whether we push from right to left
or left to right isn’t relevant here for an obvious reason that’s left as an exercise to
the reader.
Add XCMDGlue.c to the project rather than including it at the end of the XCMDS
source code as is the case with MPW. I like this feature of Think “C” - you keep track
of the source modules at the project level and not at the source code level.
• Creating the resource
Once all modules compile,use the “Create Code Resource” menu option to create
the code resource. LightspeedC presents a dialogue asking for the name of an output
file. Enter the name of a file but not your output stack: LightspeedC completely erases
the previous contents of both forks of the output file before writing out the code
resource!
Select the “smart link” option when creating a code resource. Your links will be
slower, but your XCMDs will be quite a bit smaller (The example in listing 3 compiles
to 12.5K with smart link of and 2.5K with smart link on). Of course, you can speed up
turnaround during development by leaving this option unselected.
That’s it! Use ResEdit or Rescopy to copy the XCMD into your stack. Perhaps
LightspeedC will release a future version of “C” that will build code resources
directly into the target file (if you try this now, the entire contents of the target file
will be lost). In the meantime, the extra step needed to copy the resource into your
stack is a small price to pay for an outstanding development system.
I hope the information in this article will help those of you who need to create an
XCMD interface for another development system. The process is really rather simple
and it provides you one of those rare opportunities in programming where you can get
a lot done without doing a lot of work!
Listing 1:
/********************************/
/* File: SimpleXCMD.c */
/* */
/* This is what a simple XCMD */
/* written in Lightspeed “C” */
/* In order to build this code */
/* resource, you will need the */
/* two files “HyperXCMD.h” and */
/* XCMDGlue.c. */
/* */
/* ---------------------------- */
/* To Build: */
/* */
/* (1) Create a project using */
/* this file as well as the */
/* XCMD.Glue.c file. (Set */
/* project type to XCMD (or */
/* XFCN) from the Project menu. */
/* */
/* (2) Bring the project up to */
/* date. */
/* */
/* (3) Build Code Resource. */
/* */
/* (4) Use ResEdit to copy the */
/* resource to your stack. */
/********************************/
#include
#include
#include
#include
#include
#include “HyperXCmd.h”
pascal void main( paramPtr )
XCmdBlockPtr paramPtr;
{
char theString[256]; /* A “Pascal” String */
theString[0] = ‘\0x0B’; /* Remember, static*/
theString[1] = ‘H’; /* strings are placed*/
theString[2] = ‘E’; /* in the global pool*/
theString[3] = ‘L’; /* CODE resources such */
theString[4] = ‘L’; /* as XCMDS and XFCNS*/
theString[5] = ‘O’; /* don’t have access to */
theString[6] = ‘ ‘; /* globals so you have*/
theString[7] = ‘W’; /* to discretely set the*/
theString[8] = ‘O’; /* string’s value */
theString[9] = ‘R’;
theString[10]= ‘L’;
theString[11]= ‘D’;
/* A sample callback example */
paramPtr->returnValue = PasToZero( paramPtr, &theString );
}
Listing 2:
/************************************/
/* File: HyperXCmd.h */
/* */
/* Interface for standard */
/* HyperCard callback routines. */
/* */
/* Based on original work by */
/* Dan Winkler of Apple Computer */
/* */
/************************************/
typedef struct Str31 {
char data[32];
} Str31;
typedef Str31 * Str31Ptr;
typedef struct XCmdBlock {
short paramCount;
Handle params[16];
Handle returnValue;
Boolean passFlag;

void (*entryPoint)();
short request;
short result;
long inArgs[8];
long outArgs[4];
} XCmdBlock;
typedef XCmdBlock *XCmdBlockPtr;
/* Callback codes */
#define xresSucc 0
#define xresFail 1
#define xresNotImp 2

/* Callback request codes */
#define xreqSendCardMessage 1
#define xreqEvalExpr 2
#define xreqStringLength 3
#define xreqStringMatch 4
#define xreqZeroBytes 6
#define xreqPasToZero 7
#define xreqZeroToPas 8
#define xreqStrToLong 9
#define xreqStrToNum 10
#define xreqStrToBool 11
#define xreqStrToExt 12
#define xreqLongToStr 13
#define xreqNumToStr 14
#define xreqNumToHex 15
#define xreqBoolToStr 16
#define xreqExtToStr 17
#define xreqGetGlobal 18
#define xreqSetGlobal 19
#define xreqGetFieldByName 20
#define xreqGetFieldByNum 21
#define xreqGetFieldByID 22
#define xreqSetFieldByName 23
#define xreqSetFieldByNum 24
#define xreqSetFieldByID 25
#define xreqStringEqual 26
#define xreqReturnToPas 27
#define xreqScanToReturn 28
#define xreqScanToZero 39
/*
“Prototypes” for the Callbacks. Project
must include XCmdGlue.c.